在開發應用程式的過程裡,我們通常會定義 Model
的類別用來處理資料結構或是資料儲存上的使用。
舉個簡單的例子,定義一個 JSON { "name": "Leo" }
資料的 Model 類別
class User {
User({
required this.name,
});
final String name;
factory User.fromJson(Map<String, dynamic> json) => User(
name: json["name"],
);
Map<String, dynamic> toJson() => {
"name": name,
};
}
void main() {
var userData = { "name": "Leo" };
var user = User.fromJson(userData);
print(user.toJson().toString()); // {name: Leo}
}
資料的安全性:在開發的過程我們雖然也可以從 Map 型別取資料,但是無法掌握 value 對應的真實型別
資料的管理與維護:隨著專案大小,定義清楚的 Model 類別有助於開發與維護的工作進行
我們來看看氣象資料開放平臺 API 回傳的 json 資料結構大致如下,可以看到我們我們至少就有5層的 Model 需要定義
{
"success": "true",
"records": {
"locations": [{
"datasetDescription": "臺灣各縣市鄉鎮未來3天(72小時)逐3小時天氣預報",
"locationsName": "臺北市",
"dataid": "D0047-061",
"location": [{
"locationName": "中正區",
"geocode": "63000050",
"lat": "25.046058",
"lon": "121.516565",
"weatherElement": [{
"elementName": "Wx",
"description": "天氣現象",
"time": [{
"startTime": "2021-09-16 06:00:00",
"endTime": "2021-09-16 09:00:00",
"elementValue": [{
"value": "短暫陣雨或雷雨",
"measures": "自定義 Wx 文字"
},
{
"value": "15",
"measures": "自定義 Wx 單位"
}
]
}]
}]
}]
}]
}
}
這個 API 回傳的資料結構包含了某地區、某時間、某天氣因子、某單位…,在取用資料上相對麻煩,
我們可以透過 Model 類別來處理回傳的資料格式,並定義一些方法來取得資料內容,如下:
var data = await WetherAPI().fetch(service, parameters: params);
// json_serializable
var weather = WeatherModel.fromJson(data);
// first 自定義屬性用來取得第一筆
var record = weather.records.first;
// 自定義 element 方法用來取得天氣因子
var description = record.element("WeatherDescription").now.values[0].value;
var wx = record.element("Wx").now.values[1].value;
簡單的資料結構我們可以手動寫 Code 處理,不過實務上的資料結構通常比較複雜,我們可以使用一些工具協助創建 Model 類別,將開發時間留給業務邏輯的處理而不是資料的建模。
json_serializable - 需手動定義好資料的結構,可以自動產生序列化相關的程式碼。
quicktype - 提供 json 的資料內容,會自動化產生 Model 的程式碼,需檢查上下內容是否符合自己預期。
透過自動化的轉換,我們使用 quicktype
提供 json 格式自動化產生對應的 Model
完整程式碼如下:
// To parse this JSON data, do
//
// final weatherApiResponse = weatherApiResponseFromJson(jsonString);
import 'package:meta/meta.dart';
import 'dart:convert';
WeatherApiResponse weatherApiResponseFromJson(String str) => WeatherApiResponse.fromJson(json.decode(str));
String weatherApiResponseToJson(WeatherApiResponse data) => json.encode(data.toJson());
class WeatherApiResponse {
WeatherApiResponse({
required this.success,
required this.records,
});
final String success;
final Records records;
factory WeatherApiResponse.fromJson(Map<String, dynamic> json) => WeatherApiResponse(
success: json["success"],
records: Records.fromJson(json["records"]),
);
Map<String, dynamic> toJson() => {
"success": success,
"records": records.toJson(),
};
}
class Records {
Records({
required this.locations,
});
final List<RecordsLocation> locations;
factory Records.fromJson(Map<String, dynamic> json) => Records(
locations: List<RecordsLocation>.from(json["locations"].map((x) => RecordsLocation.fromJson(x))),
);
Map<String, dynamic> toJson() => {
"locations": List<dynamic>.from(locations.map((x) => x.toJson())),
};
}
class RecordsLocation {
RecordsLocation({
required this.datasetDescription,
required this.locationsName,
required this.dataid,
required this.location,
});
final String datasetDescription;
final String locationsName;
final String dataid;
final List<LocationLocation> location;
factory RecordsLocation.fromJson(Map<String, dynamic> json) => RecordsLocation(
datasetDescription: json["datasetDescription"],
locationsName: json["locationsName"],
dataid: json["dataid"],
location: List<LocationLocation>.from(json["location"].map((x) => LocationLocation.fromJson(x))),
);
Map<String, dynamic> toJson() => {
"datasetDescription": datasetDescription,
"locationsName": locationsName,
"dataid": dataid,
"location": List<dynamic>.from(location.map((x) => x.toJson())),
};
}
class LocationLocation {
LocationLocation({
required this.locationName,
required this.geocode,
required this.lat,
required this.lon,
required this.weatherElement,
});
final String locationName;
final String geocode;
final String lat;
final String lon;
final List<WeatherElement> weatherElement;
factory LocationLocation.fromJson(Map<String, dynamic> json) => LocationLocation(
locationName: json["locationName"],
geocode: json["geocode"],
lat: json["lat"],
lon: json["lon"],
weatherElement: List<WeatherElement>.from(json["weatherElement"].map((x) => WeatherElement.fromJson(x))),
);
Map<String, dynamic> toJson() => {
"locationName": locationName,
"geocode": geocode,
"lat": lat,
"lon": lon,
"weatherElement": List<dynamic>.from(weatherElement.map((x) => x.toJson())),
};
}
class WeatherElement {
WeatherElement({
required this.elementName,
required this.description,
required this.time,
});
final String elementName;
final String description;
final List<Time> time;
factory WeatherElement.fromJson(Map<String, dynamic> json) => WeatherElement(
elementName: json["elementName"],
description: json["description"],
time: List<Time>.from(json["time"].map((x) => Time.fromJson(x))),
);
Map<String, dynamic> toJson() => {
"elementName": elementName,
"description": description,
"time": List<dynamic>.from(time.map((x) => x.toJson())),
};
}
class Time {
Time({
required this.startTime,
required this.endTime,
required this.elementValue,
});
final DateTime startTime;
final DateTime endTime;
final List<ElementValue> elementValue;
factory Time.fromJson(Map<String, dynamic> json) => Time(
startTime: DateTime.parse(json["startTime"]),
endTime: DateTime.parse(json["endTime"]),
elementValue: List<ElementValue>.from(json["elementValue"].map((x) => ElementValue.fromJson(x))),
);
Map<String, dynamic> toJson() => {
"startTime": startTime.toIso8601String(),
"endTime": endTime.toIso8601String(),
"elementValue": List<dynamic>.from(elementValue.map((x) => x.toJson())),
};
}
class ElementValue {
ElementValue({
required this.value,
required this.measures,
});
final String value;
final String measures;
factory ElementValue.fromJson(Map<String, dynamic> json) => ElementValue(
value: json["value"],
measures: json["measures"],
);
Map<String, dynamic> toJson() => {
"value": value,
"measures": measures,
};
}